package minio

import (
	"context"
	"fmt"
	"github.com/minio/minio-go/v7"
	"github.com/minio/minio-go/v7/pkg/credentials"
	"go.uber.org/zap"
	"io"
	"time"
)

type MinioClient struct {
	Client *minio.Client
	Bucket string
}

type Options struct {
	Endpoint     string
	Creds        *credentials.Credentials
	Secure       bool
	Region       string
	BucketLookup minio.BucketLookupType
	Bucket       string
	Size         int64
}

func NewMinioClient(opts Options) (*MinioClient, error) {
	if opts.Endpoint == "" || opts.Creds == nil || opts.Bucket == "" {
		err := fmt.Errorf("endpoint, credentials, or bucket is missing")
		zap.S().Error(err.Error())
		return nil, err
	}

	minioOpts := &minio.Options{
		Creds:        opts.Creds,
		Secure:       opts.Secure,
		Region:       opts.Region,
		BucketLookup: opts.BucketLookup,
	}

	minioClient, err := minio.New(opts.Endpoint, minioOpts)
	if err != nil {
		zap.S().Errorf("failed to create minio client: %v", err)
		return nil, err
	}

	ctx := context.Background()
	exists, err := minioClient.BucketExists(ctx, opts.Bucket)
	if err != nil {
		zap.S().Errorf("check bucket %s existence failed: %v", opts.Bucket, err)
		return nil, err
	}
	if !exists {
		err = minioClient.MakeBucket(ctx, opts.Bucket, minio.MakeBucketOptions{Region: opts.Region})
		if err != nil {
			zap.S().Errorf("create bucket %s failed: %v", opts.Bucket, err)
			return nil, err
		}
	}

	return &MinioClient{
		Client: minioClient,
		Bucket: opts.Bucket,
	}, nil
}

func getStoragePath() string {
	now := time.Now()
	return fmt.Sprintf("%d/%02d/%02d/", now.Year(), now.Month(), now.Day())
}

func (m *MinioClient) UploadFile(ctx context.Context, file io.Reader, key string, contentType string) (string, error) {
	storagePath := getStoragePath()
	fullKey := storagePath + key

	_, err := m.Client.PutObject(ctx, m.Bucket, fullKey, file, -1, minio.PutObjectOptions{
		ContentType: contentType,
	})
	if err != nil {
		zap.S().Errorf("upload object %s to bucket %s failed: %v", fullKey, m.Bucket, err)
		return "", err
	}

	// 返回永久 URL
	baseURL := m.Client.EndpointURL().String()
	permanentURL := fmt.Sprintf("%s/%s/%s", baseURL, m.Bucket, fullKey)

	zap.S().Infof("Successfully uploaded %s to bucket %s, URL: %s", fullKey, m.Bucket, permanentURL)
	return permanentURL, nil
}

// 其他方法保持不变...

// DownloadFile 下载文件
func (m *MinioClient) DownloadFile(ctx context.Context, key string, w io.Writer) error {
	object, err := m.Client.GetObject(ctx, m.Bucket, key, minio.GetObjectOptions{})
	if err != nil {
		zap.S().Errorf("get object %s from bucket %s failed: %v", key, m.Bucket, err)
		return err
	}
	defer object.Close()

	_, err = io.Copy(w, object)
	if err != nil {
		zap.S().Errorf("copy object %s to writer failed: %v", key, err)
		return err
	}

	zap.S().Infof("Successfully downloaded %s from bucket %s", key, m.Bucket)
	return nil
}

// DeleteFile 删除文件
func (m *MinioClient) DeleteFile(ctx context.Context, key string) error {
	err := m.Client.RemoveObject(ctx, m.Bucket, key, minio.RemoveObjectOptions{})
	if err != nil {
		zap.S().Errorf("remove object %s from bucket %s failed: %v", key, m.Bucket, err)
		return err
	}

	zap.S().Infof("Successfully deleted %s from bucket %s", key, m.Bucket)
	return nil
}

// ListObjects 列出文件
func (m *MinioClient) ListObjects(ctx context.Context, prefix string, limit int) ([]string, error) {
	var objectNames []string
	count := 0

	for object := range m.Client.ListObjects(ctx, m.Bucket, minio.ListObjectsOptions{
		Prefix:    prefix,
		Recursive: true,
	}) {
		if object.Err != nil {
			return nil, object.Err
		}
		objectNames = append(objectNames, object.Key)
		count++
		if limit > 0 && count >= limit {
			break
		}
	}

	return objectNames, nil
}

// GetPresignedGetObject 返回对象的 URL 地址，有效期时间为 expires
func (m *MinioClient) GetPresignedGetObject(ctx context.Context, objectName string, expires time.Duration) (string, error) {
	if expires <= 0 {
		expires = time.Hour * 24 // 默认 24 小时
	}

	// 检查对象是否存在
	_, err := m.Client.StatObject(ctx, m.Bucket, objectName, minio.StatObjectOptions{})
	if err != nil {
		zap.S().Errorf("stat object %s failed: %v", objectName, err)
		return "", err
	}

	url, err := m.Client.PresignedGetObject(ctx, m.Bucket, objectName, expires, nil)
	if err != nil {
		zap.S().Errorf("get presigned URL for %s failed: %v", objectName, err)
		return "", err
	}

	zap.S().Infof("Successfully generated presigned URL for %s", objectName)
	return url.String(), nil
}
